#include "Level1Manager.h"
#include "Level1.h"
#include "Transformation.h"
#include "HUDtimer.h"
#include "Counter.h"
#include "ColorRGB.h"
#include "Flocking.h"
#include "SimpleText.h"
#include "HUDlife.h"
#include "Helpers.hpp"
#include "Logger.h"
#include "Exception.h"

using namespace game_engine;
using namespace game_engine::components;
using namespace game_engine::library;
using namespace game_engine::utils;

vector<Point> goalPositions;
UINT32 lastPosition;

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

Level1Manager::Level1Manager(void)
    : SceneManager(), timerValue(0.0f), _levelComplete(false)
{
	slotTimerUpdated.AssignFunction(this, &Level1Manager::timerUpdated);
}

Level1Manager::Level1Manager(Level1 *parent)
    : SceneManager(parent), timerValue(0.0f), _levelComplete(false)
{
	slotTimerUpdated.AssignFunction(this, &Level1Manager::timerUpdated);
}

Level1Manager::~Level1Manager(void)
{
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

void Level1Manager::initialize(void) throw(...)
{
    // This is where all connections to signals are defined
    // IMPORTANT : all objects connecting to the addMovedObject slot, must have
    // a BoxCollider component
    vector<GameObject*> objs = objectsWithCollider();
    vector<GameObject*>::iterator i;
    for (i=objs.begin(); i!=objs.end(); i++)
    {
        Transformation *transf = dynamic_cast<Transformation*> 
            ((*i)->getComponent("Transformation"));
        slotMovedObject.Connect(transf->signalPositionUpdated);
    }

	// Connect to HUD timer
	HUDtimer *timer = dynamic_cast<HUDtimer*> (scene->getObjectByName("Time"));
	Counter *c = dynamic_cast<Counter*> (timer->getComponent("Counter"));
	slotTimerUpdated.Connect(c->signalValueUpdated);

    // Connect a single slot to all signal emmitted by birds
    list<GameObject*> objects = scene->getObjects();
    list<GameObject*>::const_iterator it;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        if ((*it)->getTag() == "EnemyB")
        {
            Flocking *flock = dynamic_cast<Flocking*> ((*it)->getComponent("Flocking"));
            boost::signals2::connection c = flock->connect(flock->goalReached(), 
                boost::bind(&Level1Manager::onDestinationReached, this));
        }
    }

    // Configure goal positions
    goalPositions.push_back(Point(256.0f, 600.0f));
    goalPositions.push_back(Point(500.0f, 320.0f));
    goalPositions.push_back(Point(256.0f, 32.0f));
    goalPositions.push_back(Point(32.0f, 320.0f));
    lastPosition = 0;
    setNewGoalPosition();
}

const BOOL Level1Manager::update(void) throw(...)
{
	BOOL continueGame = true;

    // If any object has moved
    if (!movedObjects.empty())
    {
        // Get the list of moved objects which collide with the world
        vector<GameObject*> objToWorld;
        try
        {
            objToWorld = collisionArbiter.evaluateWorldCollisions(scene, 
                movedObjects);
        } catch (exception &e)
        {
            Logger::ERR(GAME_LOG, e.what());
            movedObjects.clear();
            throw RuntimeException(e.what());
        }

        // Carry out the appropriate actions with those objects
        vector<GameObject*>::iterator i;
        for (i=objToWorld.begin(); i<objToWorld.end(); i++)
        {
            processWorldCollision(*i);
        }

        // Clear list of moved objects
        movedObjects.clear();

        // Get the list of pairs of objects which collide with each other
        // All objects for which a BoxCollider had been defined must be analyzed
        vector<GameObject*> objs = objectsWithCollider();
        vector<pair<GameObject*, GameObject*>> objCollisions;
        try
        {
             objCollisions = collisionArbiter.evaluateInterCollisions(objs);
        } catch (exception &e)
        {
            Logger::ERR(GAME_LOG, e.what());
            throw RuntimeException(e.what());
        }

        // Carry out the appropriate actions with those objects
        vector<pair<GameObject*, GameObject*>>::iterator j;
        for (j=objCollisions.begin(); j<objCollisions.end(); j++)
        {
            BOOL bye = processObjectsCollision(*j);
            if (bye)
            {
                return false;
            }
        }

    } // if (!movedObjects.empty())

	// Process timer value
	if (timerValue < 0.9f)
	{
		continueGame = false;
	}
	else if (timerValue < 6.0f)
	{
		// Change timer color
		HUDtimer *timer = dynamic_cast<HUDtimer*> (scene->getObjectByName("Time"));
		timer->setTextColor(ColorRGB(255, 0, 0));
	}

    // If left mouse button has been pressed, then update goal position for all
    // EnemyB type game objects
    Point goalPosition;
    if (scene->isButtonUp(GLUT_LEFT_BUTTON, goalPosition))
    {
        list<GameObject*> objects = scene->getObjects();
        list<GameObject*>::const_iterator it;
        for(it=objects.begin(); it!=objects.end(); it++)
        {
            if ((*it)->getTag() == "EnemyB")
            {
                Flocking *flock = dynamic_cast<Flocking*> ((*it)->getComponent("Flocking"));
                flock->setGoalPosition(goalPosition);
            }
        }
    }

    return continueGame;
}

void Level1Manager::finalize(void) throw(...)
{
    // This is where all connections to signals are destroyed
    slotMovedObject.DisconnectAll();
	slotTimerUpdated.DisconnectAll();
}

///////////////////////////////////////////////////////////////////////////////
// Slots
///////////////////////////////////////////////////////////////////////////////

INT32 Level1Manager::timerUpdated(FLOAT32 newValue)
{
	timerValue = newValue;
	return 0;
}

void Level1Manager::onDestinationReached(void)
{
    setNewGoalPosition();
}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

const BOOL Level1Manager::processObjectsCollision(pair<GameObject*, GameObject*> duple)
{
    BOOL gameover = false;

    // Collision between player and EnemyA
    if ((((duple.first)->getTag() == "Player") && ((duple.second)->getTag() == "EnemyA")) ||
        (((duple.first)->getTag() == "EnemyA") && ((duple.second)->getTag() == "Player")))
    {
        // Decrement number of lifes
        HUDlife *lifeBar = dynamic_cast<HUDlife*> (scene->getObjectByName("LifeBar"));
        UINT32 val = lifeBar->getRemainingLifes();
        val -= 1;
        lifeBar->decrementLifes();

        // Game over --> level failed
        if (val == 0)
        {
            gameover = true;
            _levelComplete = false;
            return gameover;
        }
        else
        {
            // Spawn player at his initial position
            GameObject *obj = scene->getObjectByName("Player");
            Transformation *transf = dynamic_cast<Transformation*> 
                (obj->getComponent("Transformation"));
            transf->setPosition(Point(50.0, 70.0));
        }
    }

    // Collision between player and Star
    else if ((((duple.first)->getTag() == "Player") && ((duple.second)->getTag() == "Star")) ||
        (((duple.first)->getTag() == "Star") && ((duple.second)->getTag() == "Player")))
    {
        // Increment score
        GameObject *obj = scene->getObjectByName("ScoreValue");
        SimpleText *score = dynamic_cast<SimpleText*> (obj->getComponent("SimpleText"));
        INT32 val = atoi((score->getText()).c_str());
        val += STAR_SCORE;
        score->setText(to_string<INT32>(val, dec));

        // Remove Star object
        if ((duple.second)->getTag() == "Star")
        {
            scene->removeObject(duple.second);
        }
        else
        {
            scene->removeObject(duple.first);
        }

        // Look for remaining Star objects in scene
        list<GameObject*> objs = scene->getObjects();
        list<GameObject*>::iterator it;
        BOOL found = false;
        for (it=objs.begin(); it!=objs.end(); it++)
        {
            string objName = (*it)->getName();
            if (objName.find("Star") != string::npos)
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            // Game over --> level complete
            gameover = true;
            _levelComplete = true;
            return gameover;            
        }
    }

    // Collision between two EnemyA
    else if (((duple.first)->getTag() == "EnemyA") && ((duple.second)->getTag() == "EnemyA"))
    {
        // Same behaviour as of world collision
        processWorldCollision(duple.first);
        processWorldCollision(duple.second);
    }

    return gameover;
}

void Level1Manager::setNewGoalPosition(void)
{
    // Set new goal position for all flocking birds
    list<GameObject*> objects = scene->getObjects();
    list<GameObject*>::const_iterator it;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        if ((*it)->getTag() == "EnemyB")
        {
            Flocking *flock = dynamic_cast<Flocking*> ((*it)->getComponent("Flocking"));
            flock->setGoalPosition(goalPositions.at(lastPosition));
        }
    }

    // Update new goal position
    lastPosition = (lastPosition + 1) % goalPositions.size();
}

///////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////

const BOOL Level1Manager::levelComplete(void) const
{
    return _levelComplete;
}
